package lava.rt.linq.sql;


import java.lang.reflect.Field;
import java.sql.SQLDataException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;



import lava.rt.linq.LinqExecption;
import lava.rt.linq.Entity;


public abstract class TableTemplate<M extends Entity> extends ViewTemplate<M> {

	
	
	protected final String pkName;

	protected final Field pkField;
	
	//protected final Long pkFieldOffset;

	protected final Field[] insertFields, updateFields;
	
	//protected final Long[] insertFieldOffsets, updateFieldOffsets;

	protected final String sqlInsert,sqlInsertWithoutPk, sqlUpdate, sqlDelete,sqlLoad,sqlColumns;

	public TableTemplate(SqlDataContext dataContext , String tableName, String pkName,Supplier<M> entityBuilder) {
		super(dataContext, tableName, entityBuilder);
		this.pkName = pkName;

		pkField = entityFieldMap.get(pkName);
		pkField.setAccessible(true);
		
		
		List<Field> linsertFields = new ArrayList<Field>();
		for (Field f : entityFieldMap.values()) {
			String fname = f.getName();
			if (fname.equalsIgnoreCase(pkName)) {
				continue;
			}
			f.setAccessible(true);
			linsertFields.add(f);
		}
		insertFields = linsertFields.toArray(new Field[linsertFields.size()]);
		
		//insertFieldOffsets=new Long[insertFields.length];
		
		

		List<Field> lupdateFields = new ArrayList<Field>();

		for (Field f : entityFieldMap.values()) {
			String fname = f.getName();
			if (  fname.equalsIgnoreCase(pkName)) {
				continue;
			}
			f.setAccessible(true);
			lupdateFields.add(f);
		}
		updateFields = lupdateFields.toArray(new Field[lupdateFields.size()]);
		
		//updateFieldOffsets=new Long[updateFields.length];
		
		

		String sqlPattern = "insert into {0} ({1}) values ({2})", cols =  "", vals = "";

		for (Field field : insertFields) {

			String fname = field.getName();

			cols += fname + ",";
			vals += "?,";

		}
		cols = cols.substring(0,cols.length()-1);
		vals = vals.substring(0,vals.length()-1);
		sqlColumns=pkName+","+cols;
		sqlInsert = MessageFormat.format(sqlPattern, tableName, sqlColumns, "?,"+vals);
		sqlInsertWithoutPk = MessageFormat.format(sqlPattern, tableName, cols, vals);
		
		 sqlPattern = "update {0} set {1} where {2}=? ";
		 String key = "";

		

			for (Field field : updateFields) {
				String fname = field.getName();

				key += fname+" =? ,";

			}
			key = key.substring(0, key.length()-1);
			// key=LangCommon.repeat(" `{0}` =? ", ",", updateFields.length);
			sqlUpdate = MessageFormat.format(sqlPattern, this.tableName, key, this.pkName);

			
			sqlPattern = "delete from {0} where {1}=? ";

			
			sqlDelete = MessageFormat.format(sqlPattern, tableName, pkName);

			sqlLoad = MessageFormat.format("select {2} from {0} where {1}= ? ", this.tableName, this.pkName,this.sqlColumns);
	}

	public M loadByPk(Object pk) throws SqlDataContextExecption {
		Objects.requireNonNull(pk);
		String sql = sqlLoad;
		
		List<M> entrys = dataContext.<M>listEntities(entityBuilder,sql , pk);
		M ret = null;
		if (entrys.size() == 1) {
			ret = entrys.get(0);
		} 
		return ret;
	}

	public <E extends M> SqlExecuteTask insertBatch(Collection<E> entrys)  {
		
		
		
		int insertsize = insertFields.length;

		Object[][] params = new Object[entrys.size()][insertsize + 1];
		try {
			int i=0;
			
			for(M obj : entrys) {
				
				params[i][0]=obj.thisPk();
				for (int j = 0; j < insertsize; j++) {
					Field field = insertFields[j];
					params[i][j + 1] = field.get(obj);
					
				}
				i++;
			}
		} catch (Exception e) {
		//	throw new ExecuteExecption(e);
		}

		SqlExecuteTask ret=new SqlExecuteTask(sqlInsert,params) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				int re = dataContext.executeBatch(this.sql, this.params);
				return re;
			}
		};
		
		return ret;

	}
	
	
	

	
	public <E extends M> SqlExecuteTask insert(E entry) {
		int insertsize = insertFields.length;
		final Object[] _param = new Object[insertsize + 1];
		try {
			 _param[0] = entry.thisPk();
			        for (int j = 0; j < insertsize; j++) {
						Field field = insertFields[j];
						_param[j + 1] = field.get(entry);
						
					}
		} catch (Exception e) {
			//	throw new ExecuteExecption(e);
		}
		SqlExecuteTask ret=new SqlExecuteTask(sqlInsert,_param) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				int re = 0;
				
				re = dataContext.executeUpdate(this.sql, this.params[0]);
				return re;
			}
		};
		
		return ret;

	}
	

	
	public <E extends M> SqlExecuteTask update(E entry) {
		
		Object pk=entry.thisPk();
		
		Objects.requireNonNull(pk);
		
		int updatesize = updateFields.length;
		Object[] param = new Object[updatesize + 1];
		
        try {
		for (int j = 0; j < updatesize; j++) {
			Field field = updateFields[j];
			param[j] = field.get(entry);
	    }
        }catch(Exception ex) {
        	//throw new ExecuteExecption(ex);
        }
		param[updatesize] = pk;

		SqlExecuteTask ret=new SqlExecuteTask(sqlUpdate, param) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				// TODO Auto-generated method stub
				int re = dataContext.executeUpdate(this.sql, this.params[0]);
				return re;
			}
		};
			
		
		
		return ret;

	}
	
	
	public <E extends M> SqlExecuteTask updateBatch(Collection<E> entrys)  {
		
		
		
		int updatesize = updateFields.length;
		final Object[][] params = new Object[entrys.size()][updatesize + 1];
		try {
			int i=0;
			
			for(M obj:entrys) {
				

				for (int j = 0; j < updatesize; j++) {
					Field field = updateFields[j];
					params[i][j] = field.get(obj);
					
				}
				params[i][updatesize] = obj.thisPk();
                i++;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			//throw  CommandExecuteExecption.forSql(e, cmd, param)(e);
		}
		
		SqlExecuteTask ret=new SqlExecuteTask(sqlUpdate, params) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				int re =  dataContext.executeBatch(this.sql, this.params);
				return re;
			}
		};
		
		
		
		return ret;

	}
	
	
	


	
	
	
	
	 public SqlExecuteTask delete(String where,Object...param){
		 Objects.requireNonNull(where);
		 StringBuffer sql = new StringBuffer("delete from ");
		 sql
		 .append(tableName);
		 
		  sql.append(" where ").append(where);
		  
		  SqlExecuteTask ret=new SqlExecuteTask(sql.toString(),param) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				int ret = dataContext.executeUpdate(this.sql,this.params[0]);
				return ret;
			}
		};
		 
		 
    	 return ret;
	}
	 
	
	 public SqlExecuteTask update(String set,String where,Object...param){
		 Objects.requireNonNull(set);
		 Objects.requireNonNull(where);
		 StringBuffer sql = new StringBuffer("update ");
		 sql
		 .append(tableName)
		 .append(" set ")
		 .append(set)
		 ;
		 
		 sql.append(" where ").append(where);
		 
		 SqlExecuteTask ret=new SqlExecuteTask(sql.toString(),param) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				// TODO Auto-generated method stub
				int re = dataContext.executeUpdate(this.sql,this.params[0]);
				return re;
			}
		};
		 
		 
    	 return ret;
	}
	 

	
	 public <E extends M> SqlExecuteTask delete(E entry) {
			final Object pk = entry.thisPk();
			SqlExecuteTask ret=deleteByPk(pk);
			
			return ret;

		}

	
	
    public  SqlExecuteTask deleteByPk(Object pk){	
		Objects.requireNonNull(pk);
		SqlExecuteTask ret=new SqlExecuteTask(sqlDelete,new Object[] {pk}) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				int re = dataContext.executeUpdate(this.sql, this.params[0]);
				return re;
			}
		};	
		
		return ret;
	}
	
    
    public <P> SqlExecuteTask deleteBatchByPk(P[] pks)  {
		
		int dlength = pks.length;
		Object[][] params = new Object[dlength][1];
        int i=0;
		for(P pk :pks) {
			params[i][0] = pk;
			i++;
		}
		SqlExecuteTask ret=new SqlExecuteTask(sqlDelete,params) {
			
			@Override
			public int execute() throws SqlDataContextExecption {
				// TODO Auto-generated method stub
				int re = dataContext.executeBatch(this.sql,this.params);
				return re;
			}
		};
		
		
		return ret;
	}
	
	
	
	
	
	
}
